#include <vector>
#include <vd2/system/memory.h>
#include <vd2/system/cpuaccel.h>
#include <vd2/system/vdstl.h>
#include <vd2/Kasumi/pixmap.h>
#include <vd2/Kasumi/pixmaputils.h>
#include <vd2/Kasumi/pixmapops.h>

using namespace nsVDPixmap;

namespace {
	typedef void (*tpPalettedBlitter)(void *dst, ptrdiff_t dstpitch, const void *src, ptrdiff_t srcpitch, vdpixsize w, vdpixsize h, const void *pal);
	typedef void (*tpChunkyBlitter)(void *dst, ptrdiff_t dstpitch, const void *src, ptrdiff_t srcpitch, vdpixsize w, vdpixsize h);
	typedef void (*tpPlanarBlitter)(const VDPixmap& dst, const VDPixmap& src, vdpixsize w, vdpixsize h);
}

bool VDPixmapBltDirect(const VDPixmap& dst, const VDPixmap& src, vdpixsize w, vdpixsize h);

void VDPixmapBltDirectPalettedConversion(const VDPixmap& dst, const VDPixmap& src, vdpixsize w, vdpixsize h, tpPalettedBlitter pBlitter) {
	uint8 palbytes[256 * 3];

	int palsize;

	switch(src.format) {
	case kPixFormat_Pal1:
		palsize = 2;
		break;
	case kPixFormat_Pal2:
		palsize = 4;
		break;
	case kPixFormat_Pal4:
		palsize = 16;
		break;
	case kPixFormat_Pal8:
		palsize = 256;
		break;
	default:
		VDNEVERHERE;
	}

	VDASSERT(src.palette);

	VDPixmap srcpal = { (void *)src.palette, NULL, palsize, 1, 0, kPixFormat_XRGB8888 };
	VDPixmap dstpal = { palbytes, NULL, palsize, 1, 0, dst.format };

	VDVERIFY(VDPixmapBltDirect(dstpal, srcpal, palsize, 1));

	pBlitter(dst.data, dst.pitch, src.data, src.pitch, w, h, palbytes);
}

tpVDPixBltTable VDPixmapGetBlitterTable() {
#if defined(_WIN32) && defined(_M_IX86)
	static tpVDPixBltTable pBltTable;
	
	if (CPUGetEnabledExtensions() & CPUF_SUPPORTS_MMX) {
		return VDGetPixBltTableX86MMX();
	} else {
		return VDGetPixBltTableX86Scalar();
	}
#else
	static tpVDPixBltTable pBltTable = VDGetPixBltTableReference();
	return pBltTable;
#endif
}

bool VDPixmapBltDirect(const VDPixmap& dst, const VDPixmap& src, vdpixsize w, vdpixsize h) {
	if ((unsigned)src.format >= kPixFormat_Max_Standard) {
		VDASSERT(false);
		return false;
	}

	if ((unsigned)dst.format >= kPixFormat_Max_Standard) {
		VDASSERT(false);
		return false;
	}

	const VDPixmapFormatInfo& srcinfo = VDPixmapGetInfo(src.format);

	if (src.format == dst.format) {
		const int qw = -(-w >> srcinfo.qwbits);
		const int qh = -(-h >> srcinfo.qhbits);
		const int auxw = -(-w >> srcinfo.auxwbits);
		const int auxh = -(-h >> srcinfo.auxhbits);

		switch(srcinfo.auxbufs) {
		case 2:
			VDMemcpyRect(dst.data3, dst.pitch3, src.data3, src.pitch3, auxw, auxh);
		case 1:
			VDMemcpyRect(dst.data2, dst.pitch2, src.data2, src.pitch2, auxw, auxh);
		case 0:
			VDMemcpyRect(dst.data, dst.pitch, src.data, src.pitch, srcinfo.qsize * qw, qh);
		}

		return true;
	}

	void *pBlitter = VDPixmapGetBlitterTable()[src.format][dst.format];

	if (!pBlitter)
		return false;

	if (srcinfo.auxbufs > 0 || VDPixmapGetInfo(dst.format).auxbufs > 0) {
		tpPlanarBlitter p = (tpPlanarBlitter)pBlitter;

		p(dst, src, w, h);
	} else if (src.format == kPixFormat_Pal1 || src.format == kPixFormat_Pal2 || src.format == kPixFormat_Pal4 || src.format == kPixFormat_Pal8) {
		tpPalettedBlitter p = (tpPalettedBlitter)pBlitter;

		if (dst.format == kPixFormat_XRGB8888)
			p(dst.data, dst.pitch, src.data, src.pitch, w, h, src.palette);
		else
			VDPixmapBltDirectPalettedConversion(dst, src, w, h, p);
	} else {
		tpChunkyBlitter p = (tpChunkyBlitter)pBlitter;

		p(dst.data, dst.pitch, src.data, src.pitch, w, h);
	}

	return true;
}

bool VDPixmapIsBltPossible(int dst_format, int src_format) {
	if (src_format == dst_format)
		return true;

	tpVDPixBltTable tab(VDPixmapGetBlitterTable());

	if (tab[src_format][dst_format])
		return true;

	const VDPixmapFormatInfo& srcinfo = VDPixmapGetInfo(src_format);
	const VDPixmapFormatInfo& dstinfo = VDPixmapGetInfo(dst_format);

	if (srcinfo.auxbufs > 0 || dstinfo.auxbufs > 0)
		return false;		// fail, planar buffers involved (can't do scanlines independently)

	return 	  (tab[src_format][kPixFormat_YUV444_XVYU] && tab[kPixFormat_YUV444_XVYU][dst_format])
			||(tab[src_format][kPixFormat_XRGB8888] && tab[kPixFormat_XRGB8888][dst_format]);
}

bool VDPixmapBltFast(const VDPixmap& dst, const VDPixmap& src, vdpixsize w, vdpixsize h) {
	if (VDPixmapBltDirect(dst, src, w, h))
		return true;

	// Oro... let's see if we can do a two-stage conversion.
	const VDPixmapFormatInfo& srcinfo = VDPixmapGetInfo(src.format);
	const VDPixmapFormatInfo& dstinfo = VDPixmapGetInfo(dst.format);

	if (srcinfo.auxbufs > 0 || dstinfo.auxbufs > 0)
		return false;		// fail, planar buffers involved

	if (srcinfo.qh > 1)
		return false;		// fail, vertically packed formats involved

	if (srcinfo.palsize)
		return false;		// fail, paletted formats involved

	// Allocate a 4xW buffer and try round-tripping through either
	// RGB32 or XYVU.
	vdblock<uint32>		tempBuf(w + 1);

	tpVDPixBltTable tab(VDPixmapGetBlitterTable());

	void *dstp = dst.data;
	const void *srcp = src.data;

	tpChunkyBlitter pb1 = (tpChunkyBlitter)tab[src.format][kPixFormat_YUV444_XVYU];
	tpChunkyBlitter pb2 = (tpChunkyBlitter)tab[kPixFormat_YUV444_XVYU][dst.format];
	if (!pb1 || !pb2) {
		pb1 = (tpChunkyBlitter)tab[src.format][kPixFormat_XRGB8888];
		pb2 = (tpChunkyBlitter)tab[kPixFormat_XRGB8888][dst.format];
		if (!pb1 || !pb2)
			return false;
	}

	do {
		pb1(tempBuf.data(), 0, srcp, 0, w, 1);
		pb2(dstp, 0, tempBuf.data(), 0, w, 1);
		vdptrstep(srcp, src.pitch);
		vdptrstep(dstp, dst.pitch);
	} while(--h);
	return true;
}

bool VDPixmapBlt(const VDPixmap& dst, const VDPixmap& src) {
	vdpixsize w = std::min<vdpixsize>(src.w, dst.w);
	vdpixsize h = std::min<vdpixsize>(src.h, dst.h);

	if (!w || !h)
		return true;

	return VDPixmapBltFast(dst, src, w, h);
}

bool VDPixmapBlt(const VDPixmap& dst, vdpixpos x1, vdpixpos y1, const VDPixmap& src, vdpixpos x2, vdpixpos y2, vdpixsize w, vdpixsize h) {
	if (x1 < 0) {
		x2 -= x1;
		w -= x1;
		x1 = 0;
	}

	if (y1 < 0) {
		y2 -= y1;
		h -= y1;
		y1 = 0;
	}

	if (x2 < 0) {
		x1 -= x2;
		w -= x2;
		x2 = 0;
	}

	if (y2 < 0) {
		y1 -= y2;
		h -= y2;
		y2 = 0;
	}

	if (w > dst.w - x1)
		w = dst.w - x1;

	if (h > dst.h - y1)
		h = dst.h - y1;

	if (w > src.w - x2)
		w = src.w - x2;

	if (h > src.h - y2)
		h = src.h - y2;

	if (w>=0 && h >= 0) {
		VDPixmap dst2(VDPixmapOffset(dst, x1, y1));
		VDPixmap src2(VDPixmapOffset(src, x2, y2));

		return VDPixmapBltFast(dst2, src2, w, h);
	}

	return true;
}

extern bool VDPixmapStretchBltNearest_reference(const VDPixmap& dst, sint32 x1, sint32 y1, sint32 x2, sint32 y2, const VDPixmap& src, sint32 u1, sint32 v1, sint32 u2, sint32 v2);
extern bool VDPixmapStretchBltBilinear_reference(const VDPixmap& dst, sint32 x1, sint32 y1, sint32 x2, sint32 y2, const VDPixmap& src, sint32 u1, sint32 v1, sint32 u2, sint32 v2);

bool VDPixmapStretchBltNearest(const VDPixmap& dst, const VDPixmap& src) {
	return VDPixmapStretchBltNearest(dst, 0, 0, dst.w<<16, dst.h<<16, src, 0, 0, src.w<<16, src.h<<16);
}

bool VDPixmapStretchBltNearest(const VDPixmap& dst, sint32 x1, sint32 y1, sint32 x2, sint32 y2, const VDPixmap& src, sint32 u1, sint32 v1, sint32 u2, sint32 v2) {
	return VDPixmapStretchBltNearest_reference(dst, x1, y1, x2, y2, src, u1, v1, u2, v2);
}

bool VDPixmapStretchBltBilinear(const VDPixmap& dst, const VDPixmap& src) {
	return VDPixmapStretchBltBilinear(dst, 0, 0, dst.w<<16, dst.h<<16, src, 0, 0, src.w<<16, src.h<<16);
}

bool VDPixmapStretchBltBilinear(const VDPixmap& dst, sint32 x1, sint32 y1, sint32 x2, sint32 y2, const VDPixmap& src, sint32 u1, sint32 v1, sint32 u2, sint32 v2) {
	return VDPixmapStretchBltBilinear_reference(dst, x1, y1, x2, y2, src, u1, v1, u2, v2);
}
